package club.gskj.gulimall.order.config;

import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.connection.ConnectionFactory;
import org.springframework.amqp.rabbit.connection.CorrelationData;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.amqp.support.converter.Jackson2JsonMessageConverter;
import org.springframework.amqp.support.converter.MessageConverter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;

import javax.annotation.PostConstruct;

/***
 *  自定义MQ的 对象类型转换机制
 */
@Slf4j
@Configuration
public class MyRabbitConfig {

    //    @Autowired
    private RabbitTemplate rabbitTemplate;

    // TODO 自定义属性未配置配置
    @Primary
    @Bean
    public RabbitTemplate rabbitTemplate(ConnectionFactory connectionFactory) {
        RabbitTemplate template = new RabbitTemplate(connectionFactory);
        template.setMessageConverter(messageConverter());
        this.rabbitTemplate = template;

        initRabbitTemplate();
        return template;
    }


    /**
     * 使用 Jackson2JsonMessageConverter 将message消息转换为 Json格式。
     */
    @Bean
    public MessageConverter messageConverter() {
        return new Jackson2JsonMessageConverter();
    }

    /***
     *  配置 MQ消息确认机制
     *  @PostConstruct： 在MyRabbitConfig初始化完后执行
     *
     *  1.消息发送抵达到服务端确认回调
     *   1.1. 配置开启确认回调功能 spring.rabbitmq.publisher-confirms=true
     *   1.2. 配置确认回调方法 setConfirmCallback()
     *
     *  2.服务端发送消息抵达到队列确认回调
     *   1.1 配置开启消息抵达确认回调功能
     *     spring.rabbitmq.publisher-returns=true
     *     spring.rabbitmq.template.mandatory=true
     *   1.2. 配置确认回调方法 setReturnCallback()
     *
     *  3.消费端确认回调(保证每一个消息都能被正确消费，此时才可以Broker删除此消息)
     *      1. 默认是自动确认的，只要消息接收到，客户端就会自动确认，服务端则会自动删除此消息
     *          场景：
     *              当服务端收到很多消息，自动回复给MQ服务器ack。只有一个消息处理成功，此时服务器宕机，就会发生消息丢失问题；
     *              解决方法：设置为手动确认消息:
     *                  只要没有明确告诉MQ确认收货,没有Ack,消息就是一直unAcked状态,即使Consumer宕机,消息也不会丢失,会重新变为ready,下一次新的consumer连接进来就会重新发送消息到它.
     *      2. 如果签收确认消息?
     *          # 手动Ack,设置手动确认消息
     *          spring.rabbitmq.listener.simple.acknowledge-mode=manual
     *                          channel.basicAck(deliveryTag, false); 签收;业务成功完成后签收
     *                          channel.basicNack(deliveryTag, false, true); 拒签;业务执行失败,拒绝签收
     */
//    @PostConstruct
    public void initRabbitTemplate() {
        // 客户端发送消息到服务端确认回调
        rabbitTemplate.setConfirmCallback(new RabbitTemplate.ConfirmCallback() {
            /***
             *
             *  1. 只要消息抵达 Broker， ack就为true
             * @param correlationData 当前消息的唯一关联数据(唯一数据id)
             * @param ack 消息是否收到 true 服务端收到、 false 服务端未收到
             * @param cause 失败原因
             */     
            @Override
            public void confirm(CorrelationData correlationData, boolean ack, String cause) {

                // 做好消息确认机制(pulisher,consumer【设置手动Ack】)
                // 每一个发送的消息都在数据库中有记录，定时将失败消息重新再发送一遍
                log.info("confirm CorrelationData " + correlationData + " ack " + ack + " cause " + cause);
            }
        });
        // 服务端发送消息到队列确认回调
        // 触发时机，只要消息没有投递给指定的队列，就触发此方法
        rabbitTemplate.setReturnCallback(new RabbitTemplate.ReturnCallback() {
            /***
             *
             * @param message 投递的失败信息内容
             * @param replyCode  回复状态码
             * @param replyText 回复消息
             * @param exchange 发给那个交换机
             * @param routingKey 使用的路由键
             */
            @Override
            public void returnedMessage(Message message, int replyCode, String replyText, String exchange, String routingKey) {
                log.info("fail message " + message + " replyCode " + replyCode + " replyText " + replyText + " exchange " + exchange + " routingKey " + routingKey);
            }
        });
    }
}
